﻿// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Text;
using Utf8Json;

namespace SnapObjects.Data
{
    internal class UTF8JNodeWriter : INodeWriter
    {
        private class Level
        {
            public Level(bool level = false)
            {
                this.IsArray = level;
            }

            public bool IsArray { get; set; }
            public bool HasChild { get; set; }
        }

        private JsonWriter _writer;
        private readonly Stack<Level> _levels;

        public UTF8JNodeWriter()
        {
            _writer = new JsonWriter();
            _levels = new Stack<Level>();
        }

        public INodeWriter WriteAsArray(string name)
        {
            this.WritePropertyName(name);

            _writer.WriteBeginArray();

            _levels.Push(new Level(true));

            return this;
        }

        public INodeWriter WriteAsObject(string name)
        {
            this.WritePropertyName(name);

            _writer.WriteBeginObject();

            _levels.Push(new Level());

            return this;
        }

        public INodeWriter WriteAsValue(string name)
        {
            this.WritePropertyName(name);

            return this;
        }

        public void WriteEndArray()
        {
            _writer.WriteEndArray();

            _levels.Pop();
        }

        public void WriteEndObject()
        {
            _writer.WriteEndObject();

            _levels.Pop();
        }

        public void WriteNull()
        {
            _writer.WriteNull();
        }

        public void WriteRawValue(string raw)
        {
            var bytes = Encoding.UTF8.GetBytes(raw);

            _writer.WriteRaw(bytes);
        }

        public void WriteValue(object value)
        {
            if (value == null || value == DBNull.Value)
            {
                _writer.WriteNull();

                return;
            }
            else if (value is bool b)
            {
                _writer.WriteBoolean(b);
            }
            else if (value is byte be)
            {
                _writer.WriteByte(be);
            }
            else if (value is char c)
            {
                _writer.WriteByte((byte)c);
            }
            else if (value is decimal dec)
            {
                _writer.WriteDouble((double)dec);
            }
            else if (value is double d)
            {
                _writer.WriteDouble(d);
            }
            else if (value is short s)
            {
                _writer.WriteInt16(s);
            }
            else if (value is int i)
            {
                _writer.WriteInt32(i);
            }
            else if (value is long l)
            {
                _writer.WriteInt64(l);
            }
            else if (value is sbyte sb)
            {
                _writer.WriteSByte(sb);
            }
            else if (value is float f)
            {
                _writer.WriteSingle(f);
            }
            else if (value is ushort us)
            {
                _writer.WriteUInt16(us);
            }
            else if (value is uint ui)
            {
                _writer.WriteUInt32(ui);
            }
            else if (value is ulong ul)
            {
                _writer.WriteUInt64(ul);
            }
            else if (value is Enum e)
            {
                _writer.WriteUInt64(Convert.ToUInt64(e));
            }
            else if (value is DateTime t)
            {
                _writer.WriteString(t.ToString("yyyy-MM-ddTHH:mm:ss"));
            }
            else if (value is string str)
            {
                _writer.WriteString(str);
            }
            else
            {
                var v = ValueConvert.ToString(value);

                _writer.WriteString(v);
            }
        }

        private void WritePropertyName(string name)
        {
            if (_writer.CurrentOffset > 0 && _levels.Count > 0)
            {
                var level = _levels.Peek();

                if (level.HasChild)
                {
                    _writer.WriteValueSeparator();
                }
                else
                {
                    level.HasChild = true;
                }

                if (!level.IsArray)
                {
                    _writer.WritePropertyName(name);
                }
            }
        }

        public override string ToString()
        {
            return _writer.ToString();
        }
    }
}
